//go:build !gocv_specific_modules || (gocv_specific_modules && gocv_dnn)

package gocv

import (
	"image"
	"os"
	"path/filepath"
	"testing"
)

func TestReadNetDiskFromTensorflow(t *testing.T) {
	path := os.Getenv("GOCV_TENSORFLOW_TEST_FILES")
	if path == "" {
		t.Skip("Unable to locate Tensorflow model files for tests")
	}

	net := ReadNet(path+"/tensorflow_inception_graph.pb", "")
	if net.Empty() {
		t.Errorf("Unable to load Tensorflow model using ReadNet")
	}
	defer net.Close()

	checkTensorflowNet(t, net)
}

func TestReadNetMemoryFromTensorflow(t *testing.T) {
	path := os.Getenv("GOCV_TENSORFLOW_TEST_FILES")
	if path == "" {
		t.Skip("Unable to locate Tensorflow model files for tests")
	}

	bModel, err := os.ReadFile(path + "/tensorflow_inception_graph.pb")
	if err != nil {
		t.Errorf("Failed to load model from file: %v", err)
	}

	_, err = ReadNetBytes("tensorflow", nil, nil)
	if err == nil {
		t.Errorf("Should have error for reading nil model bytes")
	}

	net, err := ReadNetBytes("tensorflow", bModel, nil)
	if err != nil {
		t.Errorf("Failed to read net bytes: %v", err)
	}
	if net.Empty() {
		t.Errorf("Unable to load Tensorflow model using ReadNetBytes")
	}
	defer net.Close()

	checkTensorflowNet(t, net)
}

func TestReadNetDiskFromONNX(t *testing.T) {
	path := os.Getenv("GOCV_ONNX_TEST_FILES")
	if path == "" {
		t.Skip("Unable to locate ONNX model files for tests")
	}

	net := ReadNet(filepath.Join(path, "googlenet-9.onnx"), "")
	if net.Empty() {
		t.Errorf("Unable to load ONNX model using ReadNet")
	}
	defer net.Close()

	checkONNXNet(t, net)
}

func TestReadNetMemoryFromONNX(t *testing.T) {
	path := os.Getenv("GOCV_ONNX_TEST_FILES")
	if path == "" {
		t.Skip("Unable to locate ONNX model files for tests")
	}

	bModel, err := os.ReadFile(filepath.Join(path, "googlenet-9.onnx"))
	if err != nil {
		t.Errorf("Failed to load model from file: %v", err)
	}

	_, err = ReadNetBytes("onnx", nil, nil)
	if err == nil {
		t.Errorf("Should have error for reading nil model bytes")
	}

	net, err := ReadNetBytes("onnx", bModel, nil)
	if err != nil {
		t.Errorf("Failed to read net bytes: %v", err)
	}
	if net.Empty() {
		t.Errorf("Unable to load Caffe model using ReadNetBytes")
	}
	defer net.Close()
	checkONNXNet(t, net)
}

func checkTensorflowNet(t *testing.T, net Net) {
	img := IMRead("images/space_shuttle.jpg", IMReadColor)
	if img.Empty() {
		t.Error("Invalid Mat in Tensorflow test")
	}
	defer img.Close()

	blob := BlobFromImage(img, 1.0, image.Pt(224, 224), NewScalar(0, 0, 0, 0), true, false)
	if blob.Empty() {
		t.Error("Invalid blob in Tensorflow test")
	}
	defer blob.Close()

	net.SetInput(blob, "input")
	prob := net.Forward("softmax2")
	defer prob.Close()
	if prob.Empty() {
		t.Error("Invalid softmax2 in Tensorflow test")
	}

	probMat := prob.Reshape(1, 1)
	defer probMat.Close()
	_, maxVal, minLoc, maxLoc := MinMaxLoc(probMat)

	if round(float64(maxVal), 0.00005) != 1.0 {
		t.Errorf("Tensorflow maxVal incorrect: %v\n", round(float64(maxVal), 0.00005))
	}

	if minLoc.X != 481 || minLoc.Y != 0 {
		t.Errorf("Tensorflow minLoc incorrect: %v\n", minLoc)
	}

	if maxLoc.X != 234 || maxLoc.Y != 0 {
		t.Errorf("Tensorflow maxLoc incorrect: %v\n", maxLoc)
	}
}

func TestTensorflowDisk(t *testing.T) {
	path := os.Getenv("GOCV_TENSORFLOW_TEST_FILES")
	if path == "" {
		t.Skip("Unable to locate Tensorflow model file for tests")
	}

	net := ReadNetFromTensorflow(path + "/tensorflow_inception_graph.pb")
	if net.Empty() {
		t.Errorf("Unable to load Tensorflow model")
	}
	defer net.Close()

	checkTensorflowNet(t, net)
}

func TestTensorflowMemory(t *testing.T) {
	path := os.Getenv("GOCV_TENSORFLOW_TEST_FILES")
	if path == "" {
		t.Skip("Unable to locate Tensorflow model file for tests")
	}

	b, err := os.ReadFile(path + "/tensorflow_inception_graph.pb")
	if err != nil {
		t.Errorf("Failed to load tensorflow model from file: %v", err)
	}
	net, err := ReadNetFromTensorflowBytes(b)
	if err != nil {
		t.Errorf("Failed to load Tensorflow model from bytes: %v", err)
	}
	if net.Empty() {
		t.Errorf("Unable to load Tensorflow model")
	}
	defer net.Close()

	checkTensorflowNet(t, net)
}

func TestOnnxMemory(t *testing.T) {
	path := os.Getenv("GOCV_ONNX_TEST_FILES")
	if path == "" {
		t.Skip("Unable to locate ONNX model file for tests")
	}

	b, err := os.ReadFile(filepath.Join(path, "googlenet-9.onnx"))
	if err != nil {
		t.Errorf("Failed to load ONNX from file: %v", err)
	}

	net, err := ReadNetFromONNXBytes(b)
	if err != nil {
		t.Errorf("Failed to load Tensorflow model from bytes: %v", err)
	}
	if net.Empty() {
		t.Errorf("Unable to load Tensorflow model")
	}
	defer net.Close()

	checkONNXNet(t, net)
}

func TestOnnxDisk(t *testing.T) {
	path := os.Getenv("GOCV_ONNX_TEST_FILES")
	if path == "" {
		t.Skip("Unable to locate ONNX model file for tests")
	}

	net := ReadNetFromONNX(filepath.Join(path, "googlenet-9.onnx"))
	if net.Empty() {
		t.Errorf("Unable to load ONNX model")
	}
	defer net.Close()

	checkONNXNet(t, net)
}

func checkONNXNet(t *testing.T, net Net) {
	img := IMRead("images/space_shuttle.jpg", IMReadColor)
	if img.Empty() {
		t.Error("Invalid Mat in ONNX test")
	}
	defer img.Close()

	blob := BlobFromImage(img, 1.0, image.Pt(224, 224), NewScalar(0, 0, 0, 0), true, false)
	if blob.Empty() {
		t.Error("Invalid blob in ONNX test")
	}
	defer blob.Close()

	net.SetInput(blob, "data_0")
	prob := net.Forward("prob_1")
	defer prob.Close()
	if prob.Empty() {
		t.Error("Invalid output in ONNX test")
	}

	probMat := prob.Reshape(1, 1)
	defer probMat.Close()
	_, maxVal, minLoc, maxLoc := MinMaxLoc(probMat)

	if round(float64(maxVal), 0.0005) != 0.9965 {
		t.Errorf("ONNX maxVal incorrect: %v\n", round(float64(maxVal), 0.0005))
	}

	if minLoc.X != 955 || minLoc.Y != 0 {
		t.Errorf("ONNX minLoc incorrect: %v\n", minLoc)
	}

	if maxLoc.X != 812 || maxLoc.Y != 0 {
		t.Errorf("ONNX maxLoc incorrect: %v\n", maxLoc)
	}
}

func TestBlobFromImages(t *testing.T) {
	imgs := make([]Mat, 0)

	img := IMRead("images/space_shuttle.jpg", IMReadColor)
	if img.Empty() {
		t.Error("Invalid Mat in BlobFromImages test")
	}
	defer img.Close()

	imgs = append(imgs, img)
	imgs = append(imgs, img)

	blob := NewMat()
	BlobFromImages(imgs, &blob, 1.0, image.Pt(25, 25), NewScalar(0, 0, 0, 0), false, false, MatTypeCV32F)
	defer blob.Close()

	sz := GetBlobSize(blob)
	if sz.Val1 != 2 || sz.Val2 != 3 || sz.Val3 != 25 || sz.Val4 != 25 {
		t.Errorf("GetBlobSize in BlobFromImages retrieved wrong values")
	}
}

func TestBlobFromImageGreyscale(t *testing.T) {
	img := IMRead("images/space_shuttle.jpg", IMReadGrayScale)
	if img.Empty() {
		t.Error("Invalid Mat in TestBlobFromImageGreyscale test")
	}
	defer img.Close()

	blob := BlobFromImage(img, 1.0, image.Pt(100, 100), NewScalar(0, 0, 0, 0), false, false)
	defer blob.Close()

	if blob.Empty() {
		t.Errorf("BlobFromImageGreyscale failed to create blob")
	}
}

func TestBlobFromImageWithParams(t *testing.T) {
	img := IMRead("images/space_shuttle.jpg", IMReadColor)
	if img.Empty() {
		t.Error("Invalid Mat in BlobFromImages test")
	}
	defer img.Close()

	params := NewImageToBlobParams(1.0, image.Pt(25, 25), NewScalar(0, 0, 0, 0), false, MatTypeCV32F, DataLayoutNCHW, PaddingModeCropCenter, NewScalar(0, 0, 0, 0))
	blob := BlobFromImageWithParams(img, params)
	defer blob.Close()

	sz := GetBlobSize(blob)
	if sz.Val1 != 1 || sz.Val2 != 3 || sz.Val3 != 25 || sz.Val4 != 25 {
		t.Errorf("GetBlobSize in BlobFromImagesWithParams retrieved wrong values: %v\n", sz)
	}
}

func TestBlobFromImagesWithParams(t *testing.T) {
	imgs := make([]Mat, 0)

	img := IMRead("images/space_shuttle.jpg", IMReadColor)
	if img.Empty() {
		t.Error("Invalid Mat in BlobFromImagesWithParams test")
	}
	defer img.Close()

	imgs = append(imgs, img)
	imgs = append(imgs, img)

	params := NewImageToBlobParams(1.0, image.Pt(25, 25), NewScalar(0, 0, 0, 0), false, MatTypeCV32F, DataLayoutNCHW, PaddingModeCropCenter, NewScalar(0, 0, 0, 0))
	blob := NewMat()
	BlobFromImagesWithParams(imgs, &blob, params)
	defer blob.Close()

	sz := GetBlobSize(blob)
	if sz.Val1 != 2 || sz.Val2 != 3 || sz.Val3 != 25 || sz.Val4 != 25 {
		t.Errorf("GetBlobSize in BlobFromImagesWithParams retrieved wrong values: %v\n", sz)
	}
}

func TestImagesFromBlob(t *testing.T) {
	imgs := make([]Mat, 0)

	img := IMRead("images/space_shuttle.jpg", IMReadGrayScale)
	if img.Empty() {
		t.Error("Invalid Mat in BlobFromImages test")
	}
	defer img.Close()

	imgs = append(imgs, img)
	imgs = append(imgs, img)

	blob := NewMat()
	defer blob.Close()
	BlobFromImages(imgs, &blob, 1.0, image.Pt(img.Size()[0], img.Size()[1]), NewScalar(0, 0, 0, 0), false, false, MatTypeCV32F)

	imgsFromBlob := make([]Mat, len(imgs))
	ImagesFromBlob(blob, imgsFromBlob)

	for i := 0; i < len(imgs); i++ {
		func() {
			imgFromBlob := NewMat()
			defer imgFromBlob.Close()
			imgsFromBlob[i].ConvertTo(&imgFromBlob, imgs[i].Type())
			diff := NewMat()
			defer diff.Close()
			Compare(imgs[i], imgFromBlob, &diff, CompareNE)
			nz := CountNonZero(diff)
			if nz != 0 {
				t.Error("imgFromBlob is different from img!")
			}
		}()
	}
}

func TestGetBlobChannel(t *testing.T) {
	img := NewMatWithSize(100, 100, 5+16)
	defer img.Close()

	blob := BlobFromImage(img, 1.0, image.Pt(0, 0), NewScalar(0, 0, 0, 0), true, false)
	defer blob.Close()

	ch2 := GetBlobChannel(blob, 0, 1)
	defer ch2.Close()

	if ch2.Empty() {
		t.Errorf("GetBlobChannel failed to retrieve 2nd chan of a 3channel blob")
	}
	if ch2.Rows() != img.Rows() || ch2.Cols() != img.Cols() {
		t.Errorf("GetBlobChannel: retrieved image size does not match original")
	}
}

func TestGetBlobSize(t *testing.T) {
	img := NewMatWithSize(100, 100, 5+16)
	defer img.Close()

	blob := BlobFromImage(img, 1.0, image.Pt(0, 0), NewScalar(0, 0, 0, 0), true, false)
	defer blob.Close()

	sz := GetBlobSize(blob)
	if sz.Val1 != 1 || sz.Val2 != 3 || sz.Val3 != 100 || sz.Val4 != 100 {
		t.Errorf("GetBlobSize retrieved wrong values")
	}
}

func TestParseNetBackend(t *testing.T) {
	val := ParseNetBackend("halide")
	if val != NetBackendHalide {
		t.Errorf("ParseNetBackend invalid")
	}
	val = ParseNetBackend("openvino")
	if val != NetBackendOpenVINO {
		t.Errorf("ParseNetBackend invalid")
	}
	val = ParseNetBackend("opencv")
	if val != NetBackendOpenCV {
		t.Errorf("ParseNetBackend invalid")
	}
	val = ParseNetBackend("vulkan")
	if val != NetBackendVKCOM {
		t.Errorf("ParseNetBackend invalid")
	}
	val = ParseNetBackend("cuda")
	if val != NetBackendCUDA {
		t.Errorf("ParseNetBackend invalid")
	}
	val = ParseNetBackend("crazytrain")
	if val != NetBackendDefault {
		t.Errorf("ParseNetBackend invalid")
	}
}

func TestParseNetTarget(t *testing.T) {
	val := ParseNetTarget("cpu")
	if val != NetTargetCPU {
		t.Errorf("ParseNetTarget invalid")
	}
	val = ParseNetTarget("fp32")
	if val != NetTargetFP32 {
		t.Errorf("ParseNetTarget invalid")
	}
	val = ParseNetTarget("fp16")
	if val != NetTargetFP16 {
		t.Errorf("ParseNetTarget invalid")
	}
	val = ParseNetTarget("vpu")
	if val != NetTargetVPU {
		t.Errorf("ParseNetTarget invalid")
	}
	val = ParseNetTarget("cuda")
	if val != NetTargetCUDA {
		t.Errorf("ParseNetTarget invalid")
	}
	val = ParseNetTarget("vulkan")
	if val != NetTargetVulkan {
		t.Errorf("ParseNetTarget invalid")
	}
	val = ParseNetTarget("fpga")
	if val != NetTargetFPGA {
		t.Errorf("ParseNetTarget invalid")
	}
	val = ParseNetTarget("cudafp16")
	if val != NetTargetCUDAFP16 {
		t.Errorf("ParseNetTarget invalid")
	}
	val = ParseNetTarget("idk")
	if val != NetTargetCPU {
		t.Errorf("ParseNetTarget invalid")
	}
}

func TestFP16BlobFromImage(t *testing.T) {
	img := NewMatWithSize(100, 100, 5+16)
	defer img.Close()

	data := FP16BlobFromImage(img, 1.0, image.Pt(100, 100), 0, false, false)

	if len(data) != 60000 {
		t.Errorf("FP16BlobFromImage incorrect length: %v\n", len(data))
	}

	img2 := NewMatWithSize(100, 50, 5+16)
	defer img2.Close()

	data = FP16BlobFromImage(img2, 2.0, image.Pt(50, 100), -0.1, true, false)

	if len(data) != 30000 {
		t.Errorf("FP16BlobFromImage incorrect length: %v\n", len(data))
	}
}

func TestNMSBoxes(t *testing.T) {
	img := IMRead("images/face.jpg", IMReadColor)
	if img.Empty() {
		t.Error("Invalid Mat in NMSBoxes test")
	}
	defer img.Close()

	img.ConvertTo(&img, MatTypeCV32F)

	bboxes := []image.Rectangle{
		image.Rect(53, 47, 589, 451),
		image.Rect(118, 54, 618, 450),
		image.Rect(53, 66, 605, 480),
		image.Rect(111, 65, 630, 480),
		image.Rect(156, 51, 640, 480),
	}
	scores := []float32{0.82094115, 0.7998236, 0.9809663, 0.99717456, 0.89628726}
	scoreThreshold := float32(0.5)
	nmsThreshold := float32(0.4)

	indices := NMSBoxes(bboxes, scores, scoreThreshold, nmsThreshold)

	if indices[0] != 3 {
		t.Errorf("Invalid NMSBoxes test indices: %v", indices)
	}
}

func TestNMSBoxesWithParams(t *testing.T) {
	img := IMRead("images/face.jpg", IMReadColor)
	if img.Empty() {
		t.Error("Invalid Mat in NMSBoxesWithParams test")
	}
	defer img.Close()

	img.ConvertTo(&img, MatTypeCV32F)

	bboxes := []image.Rectangle{
		image.Rect(53, 47, 589, 451),
		image.Rect(118, 54, 618, 450),
		image.Rect(53, 66, 605, 480),
		image.Rect(111, 65, 630, 480),
		image.Rect(156, 51, 640, 480),
	}
	scores := []float32{0.82094115, 0.7998236, 0.9809663, 0.99717456, 0.89628726}
	scoreThreshold := float32(0.5)
	nmsThreshold := float32(0.4)

	indices := NMSBoxesWithParams(bboxes, scores, scoreThreshold, nmsThreshold, float32(1.0), 0)

	if indices[0] != 3 {
		t.Errorf("Invalid NMSBoxesWithParams test indices: %v", indices)
	}
}
